namespace Jsl.FxCop
{
    using System.Diagnostics.Contracts;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Private fields that are only set in the constructors should be marked "readonly" in C#.
    /// </summary>
    public partial class MarkFieldReadOnly : BaseIntrospectionRule
    {
        private static readonly TypeFieldUsage types = new TypeFieldUsage();

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="MarkFieldReadOnly"/> class.
        /// </summary>
        public MarkFieldReadOnly()
            : base(typeof(MarkFieldReadOnly).Name, "Jsl.FxCop.Rules", typeof(MarkFieldReadOnly).Assembly)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Ran after all analysis has been finished.
        /// </summary>
        public override void AfterAnalysis()
        {
            base.AfterAnalysis();
            MarkFieldReadOnly.types.Clear();
        }

        /// <summary>
        /// Checks the specified field.
        /// </summary>
        /// <param name="member">The member to check.</param>
        /// <returns>The problems found if any.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "The parameters are already validated.")]
        public override ProblemCollection Check(Member member)
        {
            // Ignore anything that isn't a field.
            var field = member as Field;
            if (field == null)
            {
                return this.Problems;
            }

            // I believe this means that the field is a constant. Constants are already "readonly".
            if (field.IsLiteral)
            {
                return null;
            }

            Contract.Assume(field.DeclaringType != null);

            // Get the field usage for this type. Create it if it hasn't been already.
            var fieldUsage = MarkFieldReadOnly.types.GetFieldUsage(field.DeclaringType);

            // If the field is in the collection then it was never set or only set in a constructor.
            Instruction instruction;
            if (fieldUsage.TryGetValue(field, out instruction))
            {
                Problem problem;
                if (instruction == null)
                {
                    // Nothing ever used this field so flag it.
                    problem = new Problem(this.GetNamedResolution("NeverSet", field.Name.Name));
                }
                else if ((instruction.OpCode == OpCode.Ldfld) || (instruction.OpCode == OpCode.Ldsfld))
                {
                    // The field was never set to will always be the default value.
                    problem = new Problem(this.GetNamedResolution("NeverSet", field.Name.Name), instruction.SourceContext);
                }
                else
                {
                    // The field was only set in constructors.
                    problem = new Problem(this.GetNamedResolution("ReadOnly", field.Name.Name), instruction.SourceContext);
                }

                this.Problems.Add(problem);
            }

            return this.Problems;
        }
        #endregion Public Methods
    }
}